/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package com.flexengine.xml;

import static com.flexengine.xml.Persister.countElements;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

/**
 * @author Luann R. Athayde
 * @version 1.0
 * @since 1.0
 */
public class PersisterWriter {
    
    public void writer(Object obj, String file) throws Exception {
        writer(obj, new File(file));
    }
    
    public void writer(Object obj, File   file) throws Exception {
        writer(obj, new FileOutputStream(file));
    }
    
    public void writer(Object obj, OutputStream out) throws Exception {
        if( obj==null ) throw new NullPointerException("Object to persiste is NULL! obj = "+obj);
        Class clazz = obj.getClass();
        //System.out.println("clazz = "+obj.getClass());
        //System.out.println("obj   = "+obj);
        if ( clazz.getAnnotation(Root.class) == null ) {
            throw new NotRootTypedException("The element class["+clazz+"] is not a ROOT type!");
        }
        Root root = (Root)clazz.getAnnotation(Root.class);
        ////System.out.println("root = "+root);
        // ---------------------------------------------------------------------
        XMLOutputFactory outFactory = XMLOutputFactory.newFactory();
        XMLStreamWriter  writer     = outFactory.createXMLStreamWriter(out);
        // ---------------------------------------------------------------------
        List<Field> fields = new ArrayList<>();
        Class tmpClass = clazz;
        while( tmpClass != Object.class ) {
            Field f[] = tmpClass.getDeclaredFields();
            fields.addAll(Arrays.asList(f));
            tmpClass = tmpClass.getSuperclass();
        }
        List<Method> methods = new ArrayList<>();
        tmpClass = clazz;
        while( tmpClass != Object.class ) {
            Method m[] = tmpClass.getDeclaredMethods();
            methods.addAll(Arrays.asList(m));
            tmpClass = tmpClass.getSuperclass();
        }
        
        writer.writeStartDocument("UTF-8", "1.0");
        breakLine(writer);
        int totalElements = countElements(clazz);
        if( totalElements>0) {
            writer.writeStartElement( !root.name().isEmpty() ? root.name() : clazz.getSimpleName() );
        } else {
            writer.writeEmptyElement(!root.name().isEmpty() ? root.name() : clazz.getSimpleName() );
        }
        
        for(Field f : fields) {
            f.setAccessible(true);
            if( f.getAnnotation(Attribute.class) != null ) {
                Attribute att = f.getAnnotation(Attribute.class);
                Object value = f.get(obj);
                writer.writeAttribute(!att.name().isEmpty() ? att.name() : f.getName(), String.valueOf(value));
            }
        }
        for(Method m : methods) {
            m.setAccessible(true);
            if( m.getAnnotation(Attribute.class) != null ) {
                Attribute att = m.getAnnotation(Attribute.class);
                Object value = m.invoke(obj);
                writer.writeAttribute(!att.name().isEmpty() ? att.name() : m.getName(), String.valueOf(value));
            }
        }
        // ---------------------------------------------------------------------
        for(Field f : fields) {
            f.setAccessible(true);
            if( f.getAnnotation(Element.class)!=null ) {
                Element element = f.getAnnotation(Element.class);
                Object value = f.get(obj);
                if( value==null ) continue;
                
                //System.out.println("element field = "+f+" | value  = "+value);
                
                breakLine(writer);
                //System.out.println("writting start element...");
                writer.writeStartElement(!element.name().isEmpty() ? element.name() : f.getName());
                //System.out.println("writting element value...");
                writeElement(writer, value);
                //System.out.println("writting end element...");
                breakLine(writer);
                writer.writeEndElement();
            }
        }
        for(Method m : methods) {
            m.setAccessible(true);
            if( m.getAnnotation(Element.class)!=null ) {
                Element element = m.getAnnotation(Element.class);
                Object value = m.invoke(obj);
                if( value==null ) continue;
                
                breakLine(writer);
                writer.writeStartElement(!element.name().isEmpty() ? element.name() : m.getName());
                writeElement(writer, value);
                breakLine(writer);
                writer.writeEndElement();
            }
        }
        // ---------------------------------------------------------------------
        breakLine(writer);
        if( totalElements>0 ) {
            writer.writeEndElement();
        }
        writer.writeEndDocument();
        // ---------------------------------------------------------------------
    }
    
    private void writeElement(XMLStreamWriter writer, Object obj) throws Exception {
        if( Persister.isPrimitive(obj) ) {
            //System.out.println("Writting primitive element value...");
            breakLine(writer);
            writer.writeCharacters(String.valueOf(obj));
        } else {
            //System.out.println("Writting complex element...");
            Class elementClazz = obj.getClass();
            //System.out.println("complex element = "+elementClazz);
            if( elementClazz.isArray() ) {
                writeArrayElement(writer, obj);
                return;
            }
            if( elementClazz.getAnnotation(Root.class) == null ) {
                return;
            }
            
            Root root = (Root)elementClazz.getAnnotation(Root.class);
            
            List<Field> fields = new ArrayList<>();
            Class tmpClass = elementClazz;
            while( tmpClass != Object.class ) {
                Field f[] = tmpClass.getDeclaredFields();
                fields.addAll(Arrays.asList(f));
                tmpClass = tmpClass.getSuperclass();
            }
            List<Method> methods = new ArrayList<>();
            tmpClass = elementClazz;
            while( tmpClass != Object.class ) {
                Method m[] = tmpClass.getDeclaredMethods();
                methods.addAll(Arrays.asList(m));
                tmpClass = tmpClass.getSuperclass();
            }
            
            breakLine(writer);
            int totalElements = countElements(elementClazz);
            //System.out.println("   total elements = "+totalElements);
            if( totalElements>0) {
                //System.out.println("   Writting start element...");
                writer.writeStartElement( !root.name().isEmpty() ? root.name() : elementClazz.getSimpleName() );
            } else {
                //System.out.println("   Writting start empty element...");
                writer.writeEmptyElement(!root.name().isEmpty() ? root.name() : elementClazz.getSimpleName() );
            }
            
            //System.out.println("   Writting attributes...");
            for(Field f : fields) {
                f.setAccessible(true);
                if( f.getAnnotation(Attribute.class) != null ) {
                    Attribute att = f.getAnnotation(Attribute.class);
                    Object value = f.get(obj);
                    writer.writeAttribute(!att.name().isEmpty() ? att.name() : f.getName(), String.valueOf(value));
                }
            }
            for(Method m : methods) {
                m.setAccessible(true);
                if( m.getAnnotation(Attribute.class) != null ) {
                    Attribute att = m.getAnnotation(Attribute.class);
                    Object value = m.invoke(obj);
                    writer.writeAttribute(!att.name().isEmpty() ? att.name() : m.getName(), String.valueOf(value));
                }
            }
            // ---------------------------------------------------------------------
            if( totalElements > 0 ) {
                for(Field f : fields) {
                    f.setAccessible(true);
                    if( f.getAnnotation(Element.class)!=null ) {
                        Element tmpElement = f.getAnnotation(Element.class);
                        Object value = f.get(obj);
                        if( value==null ) continue;
                        breakLine(writer);
                        writer.writeStartElement(!tmpElement.name().isEmpty() ? tmpElement.name() : f.getName());
                        writeElement(writer, value);
                        breakLine(writer);
                        writer.writeEndElement();
                    }
                }
                for(Method m : methods) {
                    m.setAccessible(true);
                    if( m.getAnnotation(Element.class)!=null ) {
                        Element tmpElement = m.getAnnotation(Element.class);
                        Object value = m.invoke(obj);
                        if( value==null ) continue;
                        breakLine(writer);
                        writer.writeStartElement(!tmpElement.name().isEmpty() ? tmpElement.name() : m.getName());
                        writeElement(writer, value);
                        breakLine(writer);
                        writer.writeEndElement();
                    }
                }
                breakLine(writer);
                writer.writeEndElement();
            }
        }
    }
    
    private void writeArrayElement(XMLStreamWriter writer, Object obj) throws Exception {
        Object[] list = (Object[])obj;
        for (Object o : list) {
            if( o!=null && o.getClass().isArray() ) {
                writeArrayElement(writer, o);
                continue;
            }
            if( o!=null ) {
                writeElement(writer, o);
            }
        }
    }
    
    private void breakLine(XMLStreamWriter writer) throws XMLStreamException {
        writer.writeCharacters("\n");
    }
    
}
